<?php
class Forum_Db
{
    private static $_instance = null;
    
    private function __construct($params)
    {
        if (!$link = mysql_connect($params['host'], $params['username'], $params['password'])) {
            die('Could not connect: ' . mysql_error());
        }
        if (!mysql_select_db($params['dbname'], $link)) {
            die ('Can\'t use forum : ' . mysql_error());
        }
        mysql_query('SET NAMES utf8');
    }
    
    /**
     * Retrieve singleton instance of Forum_Db
     * 
     * @param array $params
     * Example: array(
     *  'host'      => '',
     *  'username'  => '',
     *  'password'  => '',
     *  'dbname'    => ''
     * )
     * @return Forum_Db
     */
    public static function getInstance($params = array())
    {
        if (null === self::$_instance) {
            self::$_instance = new self($params);
        }
        return self::$_instance;
    }
    
    public function fetchAll($select)
    {
        $resource = mysql_query($select);
        if (!$resource) {
            echo 'Could not run query: ' . mysql_error();
            exit();
        }
        $arr = array();
        while ($row = mysql_fetch_array($resource, MYSQL_ASSOC)) {
            $arr[] = $row;
        }
        mysql_free_result($resource);
        return $arr;
    }
    
    public function fetchAssoc($select)
    {
        $resource = mysql_query($select);
        if (!$resource) {
            echo 'Could not run query: ' . mysql_error();
            exit();
        }
        //@todo foreach run here
        return mysql_fetch_assoc($resource);
    }
    
    /**
     * Fetches first row
     * 
     * @param string $select
     * @return mixed
     */
    public function fetchRow($select)
    {
        $resource = mysql_query($select);
        if (!$resource) {
            echo 'Could not run query: ' . mysql_error();
            exit();
        }
        $row = mysql_fetch_assoc($resource);
        if (!count($row)) {
            return false;
        }
        return $row;
    }
    
    public function fetchOne($select)
    {
        $resource = mysql_query($select);
        if (!$resource) {
            echo 'Could not run query: ' . mysql_error();
            exit();
        }
        $result = mysql_fetch_array($resource, MYSQL_NUM);
        if (!is_array($result)) {
            return false;
        }
        return current($result);
    }
        
    /**
     * Inserts a table row with specified data.
     *
     * @param string $table The table to insert data into.
     * @param array $bind Column-value pairs.
     * @return int last inserted id
     */
    public function insert($table, array $bind)
    {
        $cols = array();
        $vals = array();
        foreach ($bind as $col => $val) {
            $cols[] = $this->quoteIdentifier($col, 'column');
            $vals[] = $this->quoteIdentifier($val, 'identifier');
        }

        $sql = "INSERT INTO "
             . $this->quoteIdentifier($table, 'column')
             . ' (' . implode(', ', $cols) . ') '
             . 'VALUES (' . implode(', ', $vals) . ')';
             
        mysql_query($sql);
        return mysql_insert_id();
    }
    
    /**
     * Updates table rows with specified data based on a WHERE clause.
     *
     * @param  string $table The table to update.
     * @param  array $bind Column-value pairs.
     * @param  mixed $where UPDATE WHERE clause(s).
     * @return bool
     */
    public function update($table, array $bind, $where = '')
    {
        $set = array();
        foreach ($bind as $col => $val) {
            $set[] = $this->quoteIdentifier($col, 'column') . 
                ' = ' . 
                $this->quoteIdentifier($val, 'identifier');
        }

        $where = $this->_whereExpr($where);

        $sql = "UPDATE "
             . $this->quoteIdentifier($table, 'column')
             . ' SET ' . implode(', ', $set)
             . (($where) ? " WHERE $where" : '');

        return mysql_query($sql);
    }
    
    /**
     * Deletes table rows based on a WHERE clause 
     * 
     * @param string $table
     * @param string $where
     * @return bool
     */
    public function delete($table, $where = '')
    {
        $where = $this->_whereExpr($where);

        $sql = "DELETE FROM "
             . $this->quoteIdentifier($table, 'column')
             . (($where) ? " WHERE $where" : '');
             
        return mysql_query($sql);
    }
    
    /**
     * Convert an array, string
     * into a string to put in a WHERE clause.
     *
     * @param mixed $where
     * @return string
     */
    protected function _whereExpr($where)
    {
        if (empty($where)) {
            return $where;
        }
        if (!is_array($where)) {
            $where = array($where);
        }
        foreach ($where as &$term) {
            $term = '(' . $term . ')';
        }
        $where = implode(' AND ', $where);
        return $where;
    }
    
    /**
     * Quotes an identifier.
     *
     * @param string|array $ident The identifier.
     * @return string The quoted identifier.
     */
    public function quoteIdentifier($ident, $mode = 'identifier')
    {
        if (is_string($ident) && $mode == 'column') {
            $ident = explode('.', $ident);
        }
        if (is_array($ident)) {
            $segments = array();
            foreach ($ident as $segment) {
                $segments[] = $this->_quoteIdentifier($segment, $mode);
            }
            $quoted = implode('.', $segments);
        } else {
            $quoted = $this->_quoteIdentifier($ident, $mode);
        }
        return $quoted;
    }
    
    /**
     * Quote an identifier.
     *
     * @param  string $value The identifier or expression.
     * @return string The quoted identifier and alias.
     */
    protected function _quoteIdentifier($value, $mode = 'identifier')
    {
        $q = $this->getQuoteIdentifierSymbol($mode);
        return ($q . str_replace("$q", "$q$q", $value) . $q);
    }
    
    /**
     * @return string
     */
    public function getQuoteIdentifierSymbol($mode = 'identifier')
    {
        switch($mode) {
            case 'identifier':
                return '"';
            case 'column':
                return "`";    
        }
    }
    
}